import { AssertTypeExecutor } from "./assertTypeExecutor"
import { addMockStepToMap } from "../common/listenPageRequest"
import { getUrlOfEnv } from "../common/urlUtil"
import { WAIT_FOR_EL_TIMEOUT, WAIT_FOR_NAVIGATE_TIMEOUT, STEP_EXECUTE_TIMEOUT } from "../common/consts"
import { capturePage } from '../common/capturePage'
import { asyncForEach } from '../common/asyncForEach'
import { WaitShowOrHideTypes, WaitTypeKeys } from '../common/enums'
import { resolveSelector } from "../common/resolveSelector"

const StepExecutorType = {
    setCookie: {
        execute: async ({ planId, suiteId, page, data, opts }) => {
            const { cookieValue: cookieListStr, cookieDomain } = data
            const cookieList = cookieListStr.split(';').map(str => ({
                domain: cookieDomain,
                name: str.split('=')[0].trim(),
                value: str.split('=').filter((_, index) => index > 0).join('').trim()
            }))

            await page.setCookie(...cookieList)

            return {
                screenshot: '',
                result: {}
            }
        }
    },
    mock: {
        execute: async ({ planId, suiteId, page, data, opts }) => {
            addMockStepToMap({ planId, suiteId, data, opts })

            return { screenshot: '', result: {} }
        }
    },
    navigate: {
        execute: async ({ planId, suiteId, page, data, opts }) => {
            const { url } = data
            const urlOfEnv = getUrlOfEnv(url, opts.env)

            await page.goto(urlOfEnv, { timeout: WAIT_FOR_NAVIGATE_TIMEOUT, waitUntil: 'networkidle2' })

            let screenshot = opts.screenshot ? await capturePage(page) : ''

            return { screenshot, result: {} }
        }
    },
    dom: {
        execute: async ({ planId, suiteId, page, data, opts }) => {
            const { userActionCode, element, x, y } = data
            const selector = resolveSelector(data)

            selector && await page.waitFor(selector => document.querySelector(selector), { timeout: WAIT_FOR_EL_TIMEOUT }, selector)

            switch (userActionCode) {
                case 'click':
                    await page.click(selector)
                    break
                case 'mouseDown':
                    await page.waitFor(selector => document.querySelector(selector).dispatchEvent(new MouseEvent('mousedown', { bubbles: true })), { timeout: WAIT_FOR_EL_TIMEOUT }, selector)
                    break
                case 'clickAtPostion':
                    await page.mouse.click(x, y)
                    break
                case 'type':
                    const { text } = data
                    await page.type(selector, text)
                    break
                case 'hover':
                    await page.hover(selector)
                    break
                case 'mouseLeave':
                    await page.waitFor(selector => document.querySelector(selector).dispatchEvent(new MouseEvent('mouseleave', { bubbles: true })), { timeout: WAIT_FOR_EL_TIMEOUT }, selector)
                    break
                case 'keyPress':
                    const { keyCodes = [] } = data
                    const elHandler = await page.$(selector)
                    await asyncForEach(keyCodes, async ({ key, code }) => {
                        await elHandler.press(key)
                    })
                    break
                default:
                    break;
            }

            let screenshot = opts.screenshot ? await capturePage(page) : ''

            return { screenshot, result: {} }
        }
    },
    wait: {
        execute: async ({ planId, suiteId, page, data, opts }) => {
            const { waitType, waitTo, timeout } = data

            if (waitType === WaitTypeKeys.duration) {
                await new Promise(resolve => setTimeout(resolve, timeout))
            } else if (waitType === WaitTypeKeys.navigation) {
                await page.waitForNavigation({ timeout, waitUntil: 'load' })
            } else {
                const selector = resolveSelector(data)

                switch (waitTo) {
                    case WaitShowOrHideTypes.Show:
                        await page.waitForSelector(selector, { timeout, visible: true })
                        break
                    case WaitShowOrHideTypes.Hide:
                        await page.waitForSelector(selector, { timeout, hidden: true })
                        break
                    default:
                        break;
                }
            }

            let screenshot = opts.screenshot ? await capturePage(page) : ''

            return { screenshot, result: {} }
        }
    },
    assert: {
        execute: async ({ planId, suiteId, page, data, opts }) => {
            const { assertTypeCode } = data
            const assertTypeExecutor = AssertTypeExecutor[assertTypeCode]

            let result
            if (assertTypeExecutor) {
                result = await AssertTypeExecutor[assertTypeCode]({ planId, suiteId, page, data, opts })
            } else {
                throw `not supported assertTypeCode: ${assertTypeCode}, only support ${Object.keys(AssertTypeExecutor).join(', ')}`
            }

            let screenshot = opts.screenshot || (typeof result === 'object' && !result.valid) ? await capturePage(page) : ''

            return {
                result: (typeof result === 'string' || Array.isArray(result)) ? result : ({ ...result }),
                screenshot
            }
        }
    },
}

export const StepExecutor = async ({ planId, suiteId, page, step, opts = {} }) => {
    const { code, data } = step
    const executor = StepExecutorType[code]
    if (executor) {
        try {
            const { timeout: optionTimeout = STEP_EXECUTE_TIMEOUT } = data
            const stepTimeout = Math.max(optionTimeout, STEP_EXECUTE_TIMEOUT)
            const timeoutReulst = '__TIMEOUT__'
            let stepExecuteTimeoutPromise = new Promise((resolve) => setTimeout(() => resolve(timeoutReulst), stepTimeout))
            let result = await Promise.race([stepExecuteTimeoutPromise, executor.execute({ planId, suiteId, page, data, opts })])
            if (result === timeoutReulst) {
                throw `execute timeout for: ${stepTimeout}ms`
            }

            return result
        } catch (error) {
            let screenshot = '' // await capturePage(page)  // 进入此处异常后往往截屏没有返回，暂时先屏蔽

            return {
                error,
                screenshot,
                result: {}
            }
        }
    } else {
        throw `unkown step code: ${code}`
    }
}